7.2.1. Передача сообщений

Идея объектно-ориентированного программирования состоит в том, что программа строится вокруг множества объектов, каждый из которых обладает собственным набором функций (операций). Вместо того чтобы представлять объект пассивным набором данных, объектно-ориентированная система позволяет объекту играть более активную роль, в частности взаимодействовать с другими объектами, обмениваясь с ними сообщениями. В результате основной упор переносится с разработки общей управляющей структуры программы, которая ответственна за порядок обращения к функциям, на конструирование самих объектов, выяснение их ролей и создание протоколов взаимодействия объектов. Эти протоколы, по существу, определяют интерфейс между объектами. Если один объект должен взаимодействовать с другим, он должен вызывать функции в строгом соответствии с этим интерфейсом.

Объекты располагают собственными данными, которые играют ту же роль, что и слоты фреймов, собственным механизмом обновления этих данных и использования хранящейся в них информации. Помимо функций интерфейса, объекты располагают собственными, "приватными" функциями, которые, как правило, представляют собой реализацию определенной родовой операции применительно к данному объекту. Помимо данных, передаваемых в качестве аргументов родовой операции, такие функции используют и локальные данные объекта — аналоги слотов фрейма.

Предположим, мы определили объект, представляющий класс ship (корабль), и наделили его свойствами x-velocity (скорость по х) и y-velocity (скорость по у). Теперь можно создать экземпляр класса ship, назвать его Titanic и одновременно присвоить свойствам x-velocity и y-velocity нового экземпляра исходные значения. Практически нет никаких отличий между этой процедурой и процедурой создания нового экземпляра фрейма, рассмотренной в предыдущей главе.

Предположим теперь, что нам понадобилось определить процедуру speed, которая будет вычислять скорость судна на основании значений свойств x-velocity и у-velocity (скорость вычисляется как корень квадратный из суммы квадратов компонентов). Такая процедура будет принадлежать абстрактному типу данных, представляющему любые суда (в терминологии языка SmallTalk speed — это метод класса ships, а в терминологии C++ — функция-член класса ships.)

Идея состоит в том, чтобы закодировать в объекте (классе) не только декларативные знания о судах, но и процедурные, т.е. методы использования декларативных знаний. Для того чтобы активизировать процедуру вычисления скорости определенного судна, в частности "Титаника", нужно передать объекту Titanic сообщение, которое побудит его обратиться к ассоциированной процедуре в контексте данных о компонентах скорости именно этого объекта. Titanic — это экземпляр класса ships, от которого он унаследовал процедуру speed. Все это представляется довольно очевидным, но описанный механизм срабатывает только в случае, если соблюдаются следующие соглашения.

Во-первых, программа, разработанная в расчете на этот механизм, должна "учредить" и следовать в дальнейшем определенному протоколу или "контракту", определяющему способ взаимодействия между объектами. Другими словами, интерфейс обмена сообщениями между объектами должен быть досконально продуман, а правила этого интерфейса жестко соблюдаться. Лучше всего продемонстрировать эту мысль на примере.

Для того чтобы определить компоненту X текущего положения "Титаника", программа должна послать запрос объекту Titanic, который имел бы следующий смысл: "передай текущее значение координаты X". Как в объекте формируется это значение или как оно хранится — дело только самого объекта и никого более не касается. Ни объекты других классов, ни какие-либо другие компоненты программы этого не знают. Более того, внутренний механизм доступа к этой информации должен быть скрыт, чтобы никто не мог добраться к ней, минуя сам объект. Это соглашение принято называть инкапсуляцией.

Во-вторых, совершенно очевидна избыточность определения своего метода вычисления скорости для каждого класса объектов, которые обладают возможностью перемещаться в двумерной системе координат. Метод, который мы только что определили для класса судов, с таким же успехом может быть использован и для других движущихся объектов, поскольку вычисление скорости представляет собой родовую операцию. Поэтому имеет смысл связать этот метод с каким-нибудь суперклассом транспортных средств, производными от которого будут классы судов, автомобилей, троллейбусов и т.п. Все эти подклассы унаследуют родовую операцию у своего базового класса.

Такое наследование — это уже нечто большее, чем когнитивная экономия или наследование свойств. Выполнение родовых операций встраивается в механизм обмена сообщениями. Отсылка сообщения— это отнюдь не вызов определенной процедуры, поскольку вызывающий объект не знает, каким именно методом отреагирует на это сообщение объект-получатель, от кого он унаследует этот метод, и будет ли вообще задействован механизм наследования в данном конкретном случае. Вызывающему объекту известны лишь наименование операции и ее внешние по отношению к преемнику аргументы. Все остальное — заботы объекта-реципиента сообщения.

7.2. Формирование объекта класса на языке CLIPS

Ниже показано, как на языке CLIPS определяется класс ship и формируется экземпляр этого класса titanic. Сначала определим класс ship, в котором имеются два слота: x-velocity и y-velocity:

(defclass ship

(is-a INITIAL_OBJECT)

(slot x-velocity (create-accessor read-write))

(slot y-velocity (create-accessor read-write)) )

Теперь сформируем экземпляр этого класса,, которому будет дано наименование "Titanic". Проще всего это сделать с помощью функции definstaces, которая в качестве аргументов принимает список параметров формируемых экземпляров. Определенные таким способом экземпляры класса будут инициализироваться при каждом перезапуске интерпретатора CLIPS.

(definstances ships (titanic of ship

(x-velocity 12) (y-velocity (10)

Завершается определение созданием обработчика событий для класса ship. Все экземпляры класса будут использовать этот обработчик для вычисления собственной скорости. Обратите внимание на то, что член в этом определении ссылается на значение слота того экземпляра класса, скорость которого требуется вычислить.

(defmessage-handler ship speed () (sqrt

( +

{ ?self:x-velocity ?self:x-velocity)

( ?self:y-velocity ?self:y-velocity)))

)

Если файл со всеми представленными выше выражениями загрузить в среду CLIPS, а затем ввести с клавиатуры (send [titanic] speed), то в ответ интерпретатор CLIPS выведет скорость объекта titanic.